package com.dkd.sbtz.service.impl;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import com.dkd.common.core.page.TableDataInfo;
import com.dkd.common.utils.SecurityUtils;
import com.dkd.common.utils.StringUtils;
import com.dkd.common.utils.sql.SqlUtil;
import com.dkd.framework.config.ServerConfig;
import com.dkd.sbtz.domain.*;
import com.dkd.sbtz.domain.ordervo.*;
import com.dkd.sbtz.domain.vo.spare.SpecialItemDTO;
import com.dkd.sbtz.mapper.AssetOrderAttachmentMapper;
import com.dkd.sbtz.mapper.SbtzMapper;
import com.dkd.sbtz.mapper.SpareMapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import com.dkd.sbtz.mapper.AssetWorkOrderMapper;
import com.dkd.sbtz.service.IAssetWorkOrderService;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * 工单列表Service业务层处理
 * 
 * @author xzj
 * @date 2025-09-09
 */
@Service
public class AssetWorkOrderServiceImpl implements IAssetWorkOrderService 
{
    @Autowired
    private AssetWorkOrderMapper assetWorkOrderMapper;

    @Autowired
    private SbtzMapper assetNodeMapper;

    @Autowired
    private SpareMapper spareMapper;

    @Autowired
    private AssetOrderAttachmentMapper assetOrderAttachmentMapper;

    @Autowired
    private ServerConfig serverConfig;
    /**
     * 查询工单列表
     * 
     * @param id 工单列表主键
     * @return 工单列表
     */
    @Override
    public AssetWorkOrder selectAssetWorkOrderById(Long id) {
        AssetWorkOrder wo = assetWorkOrderMapper.selectAssetWorkOrderById(id);
        if (wo == null) return null;

        // ====== 一对一：设备详情 ======
        if (wo.getEquipmentId() != null && wo.getEquipmentId() > 0) {
            AssetWorkOrder.AssetNodeDTO node = assetNodeMapper.selectNodeById(wo.getEquipmentId());
            wo.setNode(node);
        }

        // ====== 一对多：附件 ======
        List<AssetOrderAttachment> attachments = assetOrderAttachmentMapper.listByOrderIds(List.of(wo.getId()));
        wo.setAttachments(attachments != null ? attachments : Collections.emptyList());

        // ====== 一对多：备件明细 ======
        List<AssetWorkOrder.OrderSpareDTO> spares = assetWorkOrderMapper.listByOrderIds(List.of(wo.getId()));
        wo.setSpareInfos(spares != null ? spares : Collections.emptyList());

        return wo;
    }


    /**
     * 查询工单列表列表
     * 
     * @param assetWorkOrder 工单列表
     * @return 工单列表
     */
    @Override
    public List<AssetWorkOrder> selectAssetWorkOrderList(AssetWorkOrder assetWorkOrder)
    {
        return assetWorkOrderMapper.selectAssetWorkOrderList(assetWorkOrder);
    }

    /**
     * 新增工单列表
     * 
     * @param assetWorkOrder 工单列表
     * @return 结果
     */
    @Override
    public int insertAssetWorkOrder(AssetWorkOrder assetWorkOrder)
    {
        return assetWorkOrderMapper.insertAssetWorkOrder(assetWorkOrder);
    }

    /**
     * 修改工单列表
     * 
     * @param assetWorkOrder 工单列表
     * @return 结果
     */
    @Override
    public int updateAssetWorkOrder(AssetWorkOrder assetWorkOrder)
    {
        return assetWorkOrderMapper.updateAssetWorkOrder(assetWorkOrder);
    }

    /**
     * 批量删除工单列表
     * 
     * @param ids 需要删除的工单列表主键
     * @return 结果
     */
    @Override
    public int deleteAssetWorkOrderByIds(Long[] ids)
    {
        return assetWorkOrderMapper.deleteAssetWorkOrderByIds(ids);
    }

    /**
     * 删除工单列表信息
     * 
     * @param id 工单列表主键
     * @return 结果
     */
    @Override
    public int deleteAssetWorkOrderById(Long id)
    {
        return assetWorkOrderMapper.deleteAssetWorkOrderById(id);
    }


    /*工单报表echarts相关*/
    @Override
    public Map<String, Object> statusReport(String begin, String end) {
        Map<String, Object> ret = new HashMap<>();
        ret.put("byStatus", assetWorkOrderMapper.byStatus(begin, end));          // List<StatusCountVO>
        ret.put("dailyCreated", assetWorkOrderMapper.dailyCreated(begin, end));  // List<DailyCountVO>
        ret.put("dailyDone", assetWorkOrderMapper.dailyDone(begin, end));        // List<DailyCountVO>
        ret.put("agingBuckets", assetWorkOrderMapper.agingBuckets());            // List<AgingBucketVO>
        return ret;
    }

    @Override
    public Map<String, Object> typeReport(String begin, String end) {
        Map<String, Object> ret = new HashMap<>();
        ret.put("byType", assetWorkOrderMapper.byType(begin, end));              // List<TypeAggVO>
        ret.put("pmShare", assetWorkOrderMapper.pmShare(begin, end));            // PMShareVO
        return ret;
    }
    /*工单报表echarts相关完毕*/




    /*查询工单信息 带设备台账和备件信息*/
    @Override
    public List<ListNodeAndSpare> listNodeAndSpare(AssetWorkOrder query) {
        return assetWorkOrderMapper.selectListNodeAndSpare(query);
    }

    /*根据设备id查询工单信息*/
    public List<ListNodeAndSpare> selectByNodeId(@Param("nodeId") Long nodeId) {
        return assetWorkOrderMapper.selectByNodeId(nodeId);
    }





    /*工单 备件一起添加完毕*/

    /** 简单工单号：WOyyyyMMdd-HHmmss-随机三位；你也可换成 Redis 自增 */
    private String genWoCode() {
        return "WO" + java.time.LocalDateTime.now()
                .format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss"))
                + "-" + String.format("%03d", java.util.concurrent.ThreadLocalRandom.current().nextInt(1000));
    }


    /* --------- helpers 保持不变 --------- */
    private static List<Long> extractSpareIds(List<AssetWorkOrder.SpareItem> list) {
        return list.stream().map(AssetWorkOrder.SpareItem::getSpareId).toList();
    }
    private static List<AssetWorkOrder.SpareItem> safeList(List<AssetWorkOrder.SpareItem> list) {
        return list == null ? java.util.Collections.emptyList() : list;
    }
    private static Long firstNonNullAssetId(List<AssetWorkOrder.SpareItem> list, Long fallback) {
        Long fromList = list.stream().map(AssetWorkOrder.SpareItem::getAssetId).filter(java.util.Objects::nonNull).findFirst().orElse(null);
        return fromList != null ? fromList : fallback;
    }


    private static AssetWorkOrder mapToEntity(AssetWorkOrder req) {
        AssetWorkOrder wo = new AssetWorkOrder();
        wo.setId(req.getId());
        wo.setEquipmentId(req.getEquipmentId());
        wo.setWoCode(req.getWoCode());
        wo.setWoName(req.getWoName());
        wo.setWoContent(req.getWoContent());


        wo.setPlanStartTime(req.getPlanStartTime());
        wo.setPlanEndTime(req.getPlanEndTime());
        wo.setActualStartTime(req.getActualStartTime());
        wo.setActualEndTime(req.getActualEndTime());
        wo.setSubmitTime(req.getSubmitTime());

        wo.setTitle(req.getTitle());
        wo.setOrderType(req.getOrderType());
        wo.setUrgency(req.getUrgency());
        wo.setFaultDesc(req.getFaultDesc());

        wo.setRequesterId(req.getRequesterId());
        wo.setDispatcherId(req.getDispatcherId());
        wo.setReceiverId(req.getReceiverId());

        wo.setStatus(req.getStatus());
        wo.setSource(req.getSource());
        wo.setPriorityScore(req.getPriorityScore());
        wo.setFaultType(req.getFaultType());

        // 根据你的字段类型自行解析 plan/submit 时间（这里略）
        return wo;
    }
    /*工单 备件一起添加完毕*/



    private TableDataInfo buildTable(List<?> rows, long total) {
        TableDataInfo data = new TableDataInfo();
        data.setCode(0);
        data.setMsg("success");
        data.setRows(rows);
        data.setTotal(total);
        return data;
    }


//    @Override
//    public TableDataInfo selectOrderSpareHistory(OrderSpareHistoryQuery q) {
//        // 1) 兜底分页
//        final int pageNum  = (q.getPageNum()  == null ? 1  : q.getPageNum());
//        final int pageSize = (q.getPageSize() == null ? 10 : q.getPageSize());
//
//        // 2) 计算安全排序
//        final String orderBySql = buildSafeOrderBy(q);
//        // 可选日志：确认实际排序片段
//        // log.info("selectOrderSpareHistory orderBy => {}", orderBySql);
//
//        // 3) 仅对“查ID的分页”生效排序（不要在XML里再写 ORDER BY）
//        Page<Long> page = PageHelper.startPage(pageNum, pageSize)
//                .setOrderBy(orderBySql) // ✅ 比第三参更兼容
//                .doSelectPage(() -> assetWorkOrderMapper.selectAosIdsPage(q));
//
//        List<Long> ids = page.getResult();
//        if (ids == null || ids.isEmpty()) {
//            return buildTable(Collections.emptyList(), 0L);
//        }
//
//        // 4) 第二段详情联查：不建议再排序，以首段ID顺序为准
//        //    （如一定要完全一致，可在 MySQL 用 FIELD(a.id, …) 排，但会稍慢）
//        List<OrderSpareHistoryVO> rows = assetWorkOrderMapper.selectOrderSpareByIds(ids, q);
//
//        return buildTable(rows, page.getTotal());
//    }

    @Override
    public TableDataInfo selectOrderSpareHistory(OrderSpareHistoryQuery q) {
        final int pageNum  = (q.getPageNum()  == null ? 1  : q.getPageNum());
        final int pageSize = (q.getPageSize() == null ? 10 : q.getPageSize());

        final String orderBySql = buildSafeOrderBy(q);
        q.setOrderBySql(orderBySql); // ✅ 提供给 mapper

        Page<OrderSpareKey> page = PageHelper.startPage(pageNum, pageSize)
                .setOrderBy(orderBySql)
                .doSelectPage(() -> assetWorkOrderMapper.selectAosKeysPage(q));

        List<OrderSpareKey> keys = page.getResult();
        if (keys == null || keys.isEmpty()) {
            return buildTable(Collections.emptyList(), 0L);
        }

        List<OrderSpareHistoryVO> rows = assetWorkOrderMapper.selectOrderSpareByKeys(keys, q);
        return buildTable(rows, page.getTotal());
    }

//    private String buildSafeOrderBy(OrderSpareHistoryQuery q) {
//        // 取并净化
//        String rawCol = SqlUtil.escapeOrderBySql(q.getOrderByColumn());
//        String rawDir = q.getIsAsc();
//
//        // 前端 prop -> 实际列名（注意别名 a.）
//        Map<String, String> colMap = new HashMap<>();
//        colMap.put("createTime", "a.create_time");
//        colMap.put("assetId",    "a.asset_id");
//        colMap.put("orderId",    "a.order_id");
//        // 如需更多列，继续加：
//        // colMap.put("spareId", "a.spare_id");
//
//        // 默认列/方向
//        String col = colMap.getOrDefault(rawCol, "a.create_time");
//        String dir = "desc";
//        if ("asc".equalsIgnoreCase(rawDir))  dir = "asc";
//        if ("desc".equalsIgnoreCase(rawDir)) dir = "desc";
//
//        // tie-breaker 保证稳定性
//        return col + " " + dir + ", a.id " + dir;
//    }

    private String buildSafeOrderBy(OrderSpareHistoryQuery q) {
        String rawCol = SqlUtil.escapeOrderBySql(q.getOrderByColumn());
        String rawDir = q.getIsAsc();

        Map<String, String> colMap = new HashMap<>();
        colMap.put("createTime", "a.create_time");
        colMap.put("assetId", "a.asset_id");
        colMap.put("orderId", "a.order_id");

        String col = colMap.getOrDefault(rawCol, "a.create_time");
        String dir = "desc";
        if ("asc".equalsIgnoreCase(rawDir)) dir = "asc";

        // ✅ 无 id 时，用三个字段 tie-break，保证稳定排序
        return col + " " + dir + ", a.asset_id " + dir + ", a.order_id " + dir + ", a.spare_id " + dir;
    }



    @Override
    public List<AssetWorkOrder> selectPageAndFill(AssetWorkOrder query) {
        List<AssetWorkOrder> rows = assetWorkOrderMapper.selectPage(Map.of("q", query));
        if (rows == null || rows.isEmpty()) return rows;

        // 收集ID
        List<Long> orderIds = rows.stream().map(AssetWorkOrder::getId).toList();
        List<Long> equipmentIds = rows.stream()
                .map(AssetWorkOrder::getEquipmentId)
                .filter(Objects::nonNull)
                .filter(id -> id > 0)
                .distinct()
                .toList();

        // 设备（一对一）
        Map<Long, AssetWorkOrder.AssetNodeDTO> nodeMap = Collections.emptyMap();
        if (!equipmentIds.isEmpty()) {
            List<AssetWorkOrder.AssetNodeDTO> nodes = assetNodeMapper.listByIds(equipmentIds);
            nodeMap = nodes.stream().collect(Collectors.toMap(AssetWorkOrder.AssetNodeDTO::getId, n -> n));
        }

        // 附件（一对多）
        List<AssetOrderAttachment> allAtts = assetOrderAttachmentMapper.listByOrderIds(orderIds);
        Map<Long, List<AssetOrderAttachment>> attMap =
                allAtts.stream().collect(Collectors.groupingBy(AssetOrderAttachment::getOrderId));

        // 备件（一对多，经关系表 + 备件表）
        List<AssetWorkOrder.OrderSpareDTO> allSpares = assetWorkOrderMapper.listByOrderIds(orderIds);
        Map<Long, List<AssetWorkOrder.OrderSpareDTO>> spareMap =
                allSpares.stream().collect(Collectors.groupingBy(AssetWorkOrder.OrderSpareDTO::getOrderId));

        // 回填
        for (AssetWorkOrder r : rows) {
            r.setNode(nodeMap.get(r.getEquipmentId()));
            r.setAttachments(attMap.getOrDefault(r.getId(), Collections.emptyList()));
            r.setSpareInfos(spareMap.getOrDefault(r.getId(), Collections.emptyList()));
        }
        return rows;
    }


    private String opt(Object v) { return v == null ? "-" : String.valueOf(v); }




//    导出工单完整信息
    @Override
    public List<AssetWorkOrder> selectForExport(AssetWorkOrder query) {
        // ⚠️ 导出不要 startPage()，直接查全量
        return selectPageAndFill(query);
    }

    @Override
    public List<OrderFullExportVo> buildFullExportVo(List<AssetWorkOrder> list) {
        final String base = serverConfig.getUrl(); // http(s)://host:port
        return list.stream().map(o -> {
            OrderFullExportVo v = new OrderFullExportVo();
            v.setId(o.getId());
            v.setWoCode(o.getWoCode());
            v.setTitle(o.getTitle());
            v.setOrderType(o.getOrderType());
            v.setUrgency(o.getUrgency());
            v.setStatus(o.getStatus());
            v.setSource(o.getSource());
            v.setRequesterId(o.getRequesterId());
            v.setDispatcherId(o.getDispatcherId());
            v.setReceiverId(o.getReceiverId());
            v.setFaultDesc(o.getFaultDesc());
            v.setSubmitTime(o.getSubmitTime());
            v.setCreateTime(o.getCreateTime());
            v.setPlanStartTime(o.getPlanStartTime());
            v.setPlanEndTime(o.getPlanEndTime());
            v.setActualStartTime(o.getActualStartTime());
            v.setActualEndTime(o.getActualEndTime());
            v.setPriorityScore(o.getPriorityScore());

            // 设备
            if (o.getNode() != null) {
                v.setEquipmentName(o.getNode().getName());
                v.setEquipmentCode(o.getNode().getCode());
                v.setEquipmentModel(o.getNode().getModel());
                v.setEquipmentSpec(o.getNode().getSpecification());
                v.setEquipmentBrand(o.getNode().getBrand());
            }

            // 附件
            if (o.getAttachments() != null && !o.getAttachments().isEmpty()) {
                v.setAttachmentNames(
                        o.getAttachments().stream()
                                .map(a -> a.getFileName())
                                .collect(java.util.stream.Collectors.joining("; "))
                );
                v.setAttachmentUrls(
                        o.getAttachments().stream()
                                .map(a -> toAbsUrl(base, a.getFileUrl()))
                                .collect(java.util.stream.Collectors.joining("; "))
                );
            }

            // 备件
            if (o.getSpareInfos() != null && !o.getSpareInfos().isEmpty()) {
                v.setSpareDetail(
                        o.getSpareInfos().stream()
                                .map(s -> String.format("%s*%s(%s)",
                                        safe(s.getCode()), s.getQty() == null ? 0 : s.getQty(), safe(s.getName())))
                                .collect(java.util.stream.Collectors.joining("; "))
                );
            }
            return v;
        }).toList();
    }

    private static String safe(Object o){ return o == null ? "" : String.valueOf(o); }
    private static String toAbsUrl(String base, String fileUrl) {
        if (fileUrl == null || fileUrl.isEmpty()) return "";
        if (fileUrl.startsWith("http")) return fileUrl;
        String b = base.endsWith("/") ? base.substring(0, base.length()-1) : base;
        return b + (fileUrl.startsWith("/") ? "" : "/") + fileUrl;
    }




    /** 新增工单（支持普通/特殊备件） */
//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public Long createWithSpares(AssetWorkOrder req) {
//        String user = SecurityUtils.getUsername();
//
//        // 1️⃣ 主表插入
//        AssetWorkOrder wo = req;
//        if (StringUtils.isBlank(wo.getWoCode())) {
//            wo.setWoCode(genWoCode());
//        }
//        if (StringUtils.isBlank(wo.getStatus())) {
//            wo.setStatus("DRAFT");
//        }
//        wo.setCreateBy(user);
//        wo.setUpdateBy(user);
//
//        for (int i = 0; i < 3; i++) {
//            try {
//                assetWorkOrderMapper.insertAssetWorkOrder(wo);
//                break;
//            } catch (DuplicateKeyException ex) {
//                if (ex.getMessage() != null && ex.getMessage().contains("wo_code")) {
//                    wo.setWoCode(genWoCode());
//                    if (i == 2) throw ex;
//                } else {
//                    throw ex;
//                }
//            }
//        }
//
//        Long orderId = wo.getId();
//
//        // 2️⃣ 按 spareType 分流
//        if (Objects.equals(req.getSpareType(), 1)) {
//            handleSpecialItems(req, orderId);
//        } else {
//            handleNormalSpares(req, orderId);
//        }
//
//        return orderId;
//    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createWithSpares(AssetWorkOrder req) {
        String user = SecurityUtils.getUsername();

        // 1) 主表
        AssetWorkOrder wo = req;
        if (StringUtils.isBlank(wo.getWoCode())) {
            wo.setWoCode(genWoCode());
        }
        if (StringUtils.isBlank(wo.getStatus())) {
            wo.setStatus("DRAFT");
        }
        wo.setCreateBy(user);
        wo.setUpdateBy(user);

        for (int i = 0; i < 3; i++) {
            try {
                assetWorkOrderMapper.insertAssetWorkOrder(wo);
                break;
            } catch (DuplicateKeyException ex) {
                if (ex.getMessage() != null && ex.getMessage().contains("wo_code")) {
                    wo.setWoCode(genWoCode());
                    if (i == 2) throw ex;
                } else {
                    throw ex;
                }
            }
        }

        Long orderId = wo.getId();

        // 2) 统一清单：按 code upsert 备件 + 关系
        upsertSparesByCode(orderId, req.getEquipmentId(), nzList(req.getSpecialItems()));

        return orderId;
    }


    /** 普通模式：批量插入关系表 */
    private void handleNormalSpares(AssetWorkOrder dto, Long orderId) {
        List<AssetWorkOrder.SpareItem> items = dto.getSpares();
        if (items == null || items.isEmpty()) return;

        String user = SecurityUtils.getUsername();
        Long assetId = dto.getEquipmentId();
        assetWorkOrderMapper.upsertList(orderId, assetId, items, user);
    }


    /** 特殊模式：写入 asset_spare + asset_work_order_spare */
    private void handleSpecialItems(AssetWorkOrder dto, Long orderId) {
        if (dto.getSpecialItems() == null || dto.getSpecialItems().isEmpty()) return;

        final String user = SecurityUtils.getUsername();

        for (SpecialItemDTO it : dto.getSpecialItems()) {
            if (it == null) continue;
            String code = StringUtils.upperCase(StringUtils.trimToEmpty(it.getCode()));
            String name = StringUtils.trimToEmpty(it.getName());
            if (StringUtils.isBlank(code) || StringUtils.isBlank(name)) continue;

            Integer orderDays = Optional.ofNullable(it.getOrderDate()).orElse(0);
            Integer qty = Math.max(1, Optional.ofNullable(it.getQty()).orElse(1));

            // a) 查找是否已有
            Long spareId = spareMapper.selectIdByCode(code);
            if (spareId == null) {
                // b) 不存在 → 新增 asset_spare
                Spare s = new Spare();
                s.setCode(code);
                s.setName(name);
                s.setIsSpecial(1);
                s.setStatus(0);
                s.setUnit("个");
                s.setStockTotal(0L);
                s.setStockAvailable(0L);
                s.setOrderDate(orderDays);
                s.setCreateBy(user);
                s.setUpdateBy(user);
                spareMapper.insertSpare(s);
                spareId = s.getId();
            } else {
                // c) 已存在 → 更新基础字段
                spareMapper.updateSpecialFieldsByCode(code, name, null, orderDays, user,it.getType());
            }

            // d) 写入关系表 asset_order_spare
            assetWorkOrderMapper.insertOrUpdateWorkOrderSpare(orderId, dto.getEquipmentId(), spareId, qty, user);
        }
    }




//    @Override
//    @Transactional(rollbackFor = Exception.class)
//    public void updateWithSpares(AssetWorkOrder req) {
//        if (req.getId() == null) throw new IllegalArgumentException("工单ID不能为空");
//        final String user = SecurityUtils.getUsername();
//
//        // 1) 主表
//        AssetWorkOrder wo = mapToEntity(req);
//        wo.setId(req.getId());
//        wo.setUpdateBy(user);
//        assetWorkOrderMapper.updateAssetWorkOrder(wo);
//
//
//        // 2) 根据 spareType 路由到两种独立实现
//        Integer spareType = req.getSpareType();
//        if (spareType != null && spareType == 1) {
//            // 特殊备件（完全替换）
//            replaceOrderSpecialSpares(req.getId(), nzList(req.getSpecialItems()));
//        } else {
//            // 普通备件（支持增量/全量）
//            updateOrderNormalSpares(
//                    req.getId(),
//                    req.getEquipmentId(),
//                    nzList(req.getSpares()),
//                    Boolean.TRUE.equals(req.getReplaceSpares())
//            );
//        }
//    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateWithSpares(AssetWorkOrder req) {
        if (req.getId() == null) throw new IllegalArgumentException("工单ID不能为空");
        final String user = SecurityUtils.getUsername();

        // 1) 主表
        AssetWorkOrder wo = mapToEntity(req);
        wo.setId(req.getId());
        wo.setUpdateBy(user);
        assetWorkOrderMapper.updateAssetWorkOrder(wo);

        // 2) 统一清单：按 code 更新；replaceSpares=true 则全量替换，否则增量
        boolean replace = Boolean.TRUE.equals(req.getReplaceSpares());
        updateOrderSparesByCode(req.getId(), req.getEquipmentId(), nzList(req.getSpecialItems()), replace);
    }




    /**
     * 普通备件处理
     * @param orderId 工单ID
     * @param equipmentId 设备ID（为空时会从 items 中第一个 assetId 兜底）
     * @param items 普通备件 [{spareId, qty, assetId}]
     * @param replace true=全量替换；false=增量（只 upsert 传入项）
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateOrderNormalSpares(Long orderId,
                                        Long equipmentId,
                                        List<AssetWorkOrder.SpareItem> items,
                                        boolean replace) {
        final String user = SecurityUtils.getUsername();
        final Long assetId = firstNonNullAssetId(items, equipmentId);

        if (replace) {
            if (items == null || items.isEmpty()) {
                // 全清空
                assetWorkOrderMapper.softDeleteAll(orderId, user);
                return;
            }
            // 先软删“本次不在列表”的行，再 upsert 本次列表
            assetWorkOrderMapper.softDeleteNotIn(orderId, extractSpareIds(items), user);
            assetWorkOrderMapper.upsertList(orderId, assetId, items, user);
        } else {
            // 增量 upsert
            if (items != null && !items.isEmpty()) {
                assetWorkOrderMapper.upsertList(orderId, assetId, items, user);
            }
        }
    }


    private static <T> List<T> nzList(List<T> l){ return l==null? java.util.Collections.emptyList(): l; }


    /**
     * 特殊备件处理（完全替换：先清空关系，再按当前清单重建）
     * - 不存在的备件：创建 asset_spare(type=1)
     * - 已存在：更新名称/计划日期/采购周期
     * - 清理孤儿：对“本次移除且不再被任何工单使用”的 type=1 备件做物理删除
     */
    @Transactional(rollbackFor = Exception.class)
    public void replaceOrderSpecialSpares(Long orderId, List<SpecialItemDTO> items) {
        final String user = SecurityUtils.getUsername();
        if (items == null) items = java.util.Collections.emptyList();

        // a) 记录“原先该工单关联的特殊备件”ID集合
        List<AssetWorkOrder.OrderSpareDTO> before =
                assetWorkOrderMapper.listByOrderIds(java.util.Collections.singletonList(orderId));
        java.util.Set<Long> oldSpecialIds = before == null ? java.util.Collections.emptySet()
                : before.stream()
                .filter(x -> x.getType() != null && x.getType() == 1) // type=1=特殊
                .map(AssetWorkOrder.OrderSpareDTO::getSpareId)
                .collect(java.util.stream.Collectors.toSet());

        // b) 清空关系（软删）
        assetWorkOrderMapper.softDeleteAll(orderId, user);

        // c) 空清单 → 只做孤儿清理
        if (items.isEmpty()) {
            cleanupOrphanSpecials(oldSpecialIds, java.util.Collections.emptySet());
            return;
        }

        // d) 重建关系 + 收集本次保留的特殊ID
        java.util.List<AssetWorkOrder.SpareItem> rels = new java.util.ArrayList<>(items.size());
        java.util.Set<Long> keepIds = new java.util.HashSet<>();

        for (SpecialItemDTO it : items) {
            if (it == null) continue;
            String code = normalizeCode(it.getCode());
            String name = trim(it.getName());
            if (isBlank(code) || isBlank(name)) continue;

            // d.1 查或建 asset_spare(type=1)
            Long spareId = spareMapper.selectIdByCode(code);
            if (spareId == null) {
                Spare s = new Spare();
                s.setCode(code);
                s.setName(name);
                s.setIsSpecial(it.getType());
//                s.setPlanDate(parseDateYMD(it.getPlanDate()));
                s.setPlanDate(it.getPlanDate());
                s.setOrderDate(nz(it.getOrderDate()));
                s.setStockTotal(0L);
                s.setStockAvailable(0L);
                s.setUnit("个");
                s.setStatus(0);
                s.setRemark("");
                s.setCreateBy(user);
                s.setUpdateBy(user);
                spareMapper.insertSpare(s);
                spareId = s.getId();
            } else {
                spareMapper.updateSpecialFieldsByCode(
                        code, name, it.getPlanDate(), nz(it.getOrderDate()), user,it.getType()
                );
            }
            keepIds.add(spareId);

            // d.2 建立工单–备件关系
            AssetWorkOrder.SpareItem r = new AssetWorkOrder.SpareItem();
            r.setSpareId(spareId);
            r.setQty(it.getQty() == null || it.getQty() < 1 ? 1 : it.getQty());
            r.setAssetId(it.getAssetId());
            rels.add(r);
        }

        // e) 批量 upsert
        if (!rels.isEmpty()) {
            Long assetId = firstNonNullAssetId(rels, null);
            assetWorkOrderMapper.upsertList(orderId, assetId, rels, user);
        }

        // f) 清理“旧有但本次不再使用、且未被其他工单使用”的特殊备件
        cleanupOrphanSpecials(oldSpecialIds, keepIds);
    }

    /* ---- 特殊备件小工具（与计划页保持一致） ---- */
    private void cleanupOrphanSpecials(java.util.Set<Long> oldSpecialIds, java.util.Set<Long> keepIds) {
        if (oldSpecialIds == null || oldSpecialIds.isEmpty()) return;

        java.util.Set<Long> candidates = oldSpecialIds.stream()
                .filter(id -> !keepIds.contains(id))
                .collect(java.util.stream.Collectors.toSet());
        if (candidates.isEmpty()) return;

        java.util.List<Long> specials = spareMapper.selectSpecialIdsByIds(new java.util.ArrayList<>(candidates));
        if (specials == null || specials.isEmpty()) return;

        java.util.List<Long> inUse = assetWorkOrderMapper.selectInUseSpareIds(specials);
        java.util.Set<Long> inUseSet = inUse == null ? java.util.Collections.emptySet() : new java.util.HashSet<>(inUse);

        java.util.List<Long> toDelete = specials.stream()
                .filter(id -> !inUseSet.contains(id))
                .collect(java.util.stream.Collectors.toList());
        if (!toDelete.isEmpty()) {
            spareMapper.deleteSpareByIds(toDelete);
        }
    }

    private static String normalizeCode(String s) {
        return s == null ? null : s.toUpperCase().replaceAll("[^A-Z0-9\\-_]", "");
    }
    private static String trim(String s) { return s == null ? null : s.trim(); }
    private static boolean isBlank(String s) { return s == null || s.trim().isEmpty(); }
    private static int nz(Integer n) { return n == null ? 0 : n; }
    /** 支持前端传 yyy-MM-dd 字符串 */
    private static java.util.Date parseDateYMD(String s) {
        if (s == null || s.trim().isEmpty()) return null;
        return java.sql.Date.valueOf(java.time.LocalDate.parse(s.trim()));
    }








    /** 统一清单 item：前端不分普通/特殊，只要有 code/name/qty/orderDate */
//    private void upsertSparesByCode(Long orderId, Long equipmentId, List<SpecialItemDTO> items) {
//        if (items == null || items.isEmpty()) return;
//        final String user = SecurityUtils.getUsername();
//
//        for (SpecialItemDTO it : items) {
//            if (it == null) continue;
//            String code = normalizeCode(it.getCode());
//            String name = trim(it.getName());
//            if (isBlank(code) || isBlank(name)) continue;
//
//            Integer orderDays = nz(it.getOrderDate());
//            Integer qty = Math.max(1, nz(it.getQty()));
//
//            // ① 找是否已有该 code 的备件
//            Long spareId = spareMapper.selectIdByCode(code);
//            if (spareId == null) {
//                // ② 没有就新建（type 默认给 1；也可给 0，都不影响关系）
//                Spare s = new Spare();
//                s.setCode(code);
//                s.setName(name);
//                s.setType(it.getType());               // ✅ 随便定一个默认（以后你想清洗再改）
//                s.setOrderDate(orderDays);  // 只关心采购周期
//                s.setStockTotal(0L);
//                s.setStockAvailable(0L);
//                s.setUnit("个");
//                s.setStatus(0);
//                s.setRemark("");
//                s.setCreateBy(user);
//                s.setUpdateBy(user);
//                spareMapper.insertSpare(s);
//                spareId = s.getId();
//            } else {
//                // ③ 已存在 → 按 code 更新关心的字段（名称/采购周期）
//                // 你现有方法叫 updateSpecialFieldsByCode，也可以继续复用（planDate 传 null）
//                spareMapper.updateSpecialFieldsByCode(code, name, null, orderDays, user,it.getType());
//            }
//
//            // ④ 建立/更新工单-备件关系（ON DUPLICATE KEY UPDATE）
//            assetWorkOrderMapper.insertOrUpdateWorkOrderSpare(
//                    orderId,
//                    equipmentId,
//                    spareId,
//                    qty,
//                    user
//            );
//        }
//    }

    private void upsertSparesByCode(Long orderId, Long equipmentId, List<SpecialItemDTO> items) {
        //传空也删
        assetWorkOrderMapper.hardDeleteAll(orderId);

        if (items == null || items.isEmpty()) return;
        final String user = SecurityUtils.getUsername();

        for (SpecialItemDTO it : items) {
            if (it == null) continue;
            // ④ 建立/更新工单-备件关系（ON DUPLICATE KEY UPDATE）
            assetWorkOrderMapper.insertOrUpdateWorkOrderSpare(
                    orderId,
                    equipmentId,
                    it.getSpareId(),
                    it.getQty(),
                    user
            );
        }
    }

    /** 修改页：全量替换 or 增量 upsert（都基于 code） */
    private void updateOrderSparesByCode(Long orderId, Long equipmentId, List<SpecialItemDTO> items, boolean replace) {
        final String user = SecurityUtils.getUsername();

        if (replace) {
            // A. 全量替换：软删该工单的全部关系，然后重建
            assetWorkOrderMapper.softDeleteAll(orderId, user);
            upsertSparesByCode(orderId, equipmentId, items);
        } else {
            // B. 增量：仅对传入清单做 upsert
            upsertSparesByCode(orderId, equipmentId, items);
        }
    }



}
